package chips440.client;

import java.awt.event.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.BufferStrategy;
import javax.swing.*;
import java.io.*;
import java.applet.Applet;
import java.applet.AudioClip;
import java.net.*;
import chips440.server.Engine;
import chips440.main.*;

/**
 * This is the GUI.  It connects to the server and displays the game stuffs.
 */
public class Client extends Canvas implements KeyListener{

    /* Window size constants */
    private final int WINDOW_HEIGHT = 510;
    private final int WINDOW_WIDTH = 700;
    private final int ORIGIN = 0;
    
    /* Constants for board drawing */
    private final int OFFSET = 15;
    private final int VIEW_LIMIT = 4;
    private final int IMAGE_SIZE = 50;
    private final int TEXT_X = 500;
    private final int TIME_Y = 75;
    private final int CHIPS_Y = 150;
    private final int BOOTS_Y = 300;
    private final int KEYS_Y = 325;
    private final int LINE_DIF = 25;
    private final int BOX_SPACE = 20;
    private final int BOX_OFF = 40;
    private final int IBOX_OFF = 12;
    
    /* Indices for interpreting position array */
    private final int MY_INDEX = 0;
    private final int X = 0;
    private final int Y = 1;
    
    private final int BOOT = 0;
    private final int KEY = 1;

    private final String SONG = "CHIP01.MID";
	private final String WIN = "WINLEVEL.WAV";
    
    private final int PORT = 2048;  // I like this number thank you very much
    
    private Socket socket = null;   // Its a socket!
    
    private AudioClip theme;    // Everybody likes game music

    private static Client chips;    // Hooray for games and their various stuffs
    
    private Packet packet;      // Packet to alter and send between client and server
    
    private BufferStrategy strategy;    // Buffer strategy for pretty drawings
    
    /* A series of variables that change as the state of the game changes */
    private static boolean playing;         // Are you playing a game?
    private static boolean waiting;         // Are you waiting for a new game?
    private int[][] player_pos;                 // Where you is everyone?
    private long time_left = 0;             // How much time is left
    private int chips_left = 0;             // How many chips you need to get
    private Types.EngineState game_state;   // Have you won? Have you lost? or are you just alive?

    /**
     * Constructor for the client.  Makes a window with a menu and some pretty
     * stuff so you can play the fun game.
     */
    public Client(){
        this.playing = false;
        this.waiting = true;
        
        // Create the window
        JFrame window = new JFrame("Chips Challenge 440: Super-Win Edition");
        JPanel panel = (JPanel) window.getContentPane();
        
        setBounds(ORIGIN, ORIGIN, WINDOW_WIDTH, WINDOW_HEIGHT);
        panel.setPreferredSize(new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT));
        panel.setLayout(null);
        panel.add(this);
        window.setBounds(ORIGIN, ORIGIN, WINDOW_WIDTH, WINDOW_HEIGHT);
        
        // Add the menu to the window
        JMenuBar menu_bar = new JMenuBar();
        JMenu game = new JMenu("Game");
        game.setMnemonic(KeyEvent.VK_A);
        menu_bar.add(game);
        JMenuItem menu_item;
        
        menu_item = new JMenuItem("New Game");
        menu_item.setMnemonic(KeyEvent.VK_B);
        menu_item.setAccelerator(KeyStroke.getKeyStroke("control N"));
        menu_item.addActionListener(new NewGame());
        game.add(menu_item);
        
        game.addSeparator();
        
        menu_item = new JMenuItem("Rules");
        menu_item.setMnemonic(KeyEvent.VK_B);
        menu_item.setAccelerator(KeyStroke.getKeyStroke("control R"));
        menu_item.addActionListener(new Rules());
        game.add(menu_item);

        menu_item = new JMenuItem("About");
        menu_item.setMnemonic(KeyEvent.VK_B);
        menu_item.setAccelerator(KeyStroke.getKeyStroke("control A"));
        menu_item.addActionListener(new About());
        game.add(menu_item);

        game.addSeparator();

        menu_item = new JMenuItem("Quit");
        menu_item.setMnemonic(KeyEvent.VK_Q);
        menu_item.setAccelerator(KeyStroke.getKeyStroke("control Q"));
        menu_item.addActionListener(new Quit());
        game.add(menu_item);

        window.setJMenuBar(menu_bar);

        // Finish up the window stuff - make it close, visible, etc
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       
        window.setResizable(false);
        requestFocus();
        window.pack();
        
        createBufferStrategy(2);
        
        // Retreive the theme song (and rock out)
        this.theme = chips440.main.ResourceCache.getSound(SONG);
        theme.loop();
        window.setVisible(true);

        strategy = getBufferStrategy();
        Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics();
        graphics.drawImage(chips440.main.ResourceCache.getImage("intro"), ORIGIN, ORIGIN, this);

        strategy.show();
    }

    public static void main(String args []){
        chips = new Client();

        while(true){
            while(waiting)
                ;
            chips.game_setup();
        }
    }

    /**
     * This method sets up a new game by requesting a number of players
     * and a server IP address (if number of players is greater than 1)
     */
    private void game_setup(){
        InetAddress serverAddress;
        int numPlay;
    
        while(true){
            // popup for num players
            String response = JOptionPane.showInputDialog(null, "Enter the desired number of players:",
                    "Begin New Game", JOptionPane.PLAIN_MESSAGE);
                    
            if(response == null){
                waiting = true;
                return;
            }
                
            try{
                numPlay = Integer.parseInt(response);
            }catch(NumberFormatException n){
                numPlay = -1;
            }
        
            // popup for Severaddress
            try{
                if(numPlay > 1){
                    response = JOptionPane.showInputDialog(null, "Input IP address of server:",
                            "Begin Networked Game", JOptionPane.PLAIN_MESSAGE);
                    
                    if(response == null){
                        waiting = true;
                        return;
                    }
                    
                    serverAddress = InetAddress.getByName(response);
                }
                else
                    serverAddress = InetAddress.getLocalHost();
            }catch(Exception e){
                serverAddress = null;
            }
        
            if(serverAddress != null && numPlay > 0){
                System.out.println(numPlay+"  "+serverAddress.toString());
                playGame(numPlay, serverAddress);
                return;
            }
        }
    }
    
    /**
     * Plays the game by opening a connection to serverAddress and 
     * checking with the server to determine game state, and draw
     * the board, and check if the player has won or not.
     */
    private void playGame(int numPlay, InetAddress serverAddress){
        Process myserv = null;
        playing = true;
        
        packet = new Packet();  // Makes this packet a new one with no real info
        
        packet.numPlay = numPlay;
        
        // Step 0: if numPlay is 1, make a local server.
        if(numPlay == 1){
           /* try{
                myserv = Runtime.getRuntime().exec("java -jar server.jar "+PORT);
            }catch(Exception e){
                playing = false;
                waiting = true;
                JOptionPane.showMessageDialog(null,
                        "Failed to start single player game",
                        "ERROR!",
                        JOptionPane.PLAIN_MESSAGE);
                return;
            }*/
        }
        // Step 1: contact the server at serverAddress
        try{
            Thread.sleep(1000);
            socket = new Socket(serverAddress, PORT);
        }catch(Exception io){
            playing = false;
            waiting = true;
            io.printStackTrace();
            failureMessage("Could not open connection to server.");
            return;
        }
        // Step 2: tell server number of players
        send_packet(0, 0, null, null, Types.ChangeSet.ID);
        get_packet();
        // Step 3: Start playing!
        addKeyListener(this);
        requestFocus();
                
        while(isVisible() && playing){
            if(playing)
                get_update();  // Update the board
            if(playing)
                paint_board(); // Paint the board
            if(playing)
                state_check(); // Check state
        }
        if(myserv != null)
            myserv.destroy();
        
        try{
            send_packet(0, 0, null, null, Types.ChangeSet.QUIT);
            socket.close();
        }catch(Exception e){}
    }
    
    /**
     * Updates the known state of the game
     */
    private void get_update(){
        send_packet(0, 0, null, null, Types.ChangeSet.ALL);      // Send packet as is
        get_packet();       // Retreive packet with pos, time, chips, state
        
        // Get Position (x, y)
        player_pos = packet.positions;
        // Get Time remaining
        time_left = packet.time;
        // Get chips remaining
        chips_left = packet.chips;
        // Get game state
        game_state = packet.state;
        
        //System.out.println(game_state);
    }
    
    /**
     * Draws all the fun graphics associated with the game and
     * all the information the player should know about what's
     * going on.
     */
    private void paint_board(){
        Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics();
        graphics.setColor(Color.black);
        graphics.fillRect(ORIGIN, ORIGIN, WINDOW_WIDTH, WINDOW_HEIGHT);

        graphics.setColor(Color.white);
        graphics.drawString("Time Remaining:     " + time_left, TEXT_X, TIME_Y);
        graphics.drawString("Chips Remaining:    " + chips_left, TEXT_X, CHIPS_Y);
        
        int x_pos = TEXT_X + BOX_OFF;
        int y_boot = BOOTS_Y - IBOX_OFF;
        int y_key = KEYS_Y - IBOX_OFF;
        int item = 0;

	    graphics.setColor(Color.white);
	    graphics.drawString("Player Items", TEXT_X, BOOTS_Y - LINE_DIF);
	    graphics.drawString("Boots", TEXT_X, BOOTS_Y);
	    graphics.drawString("Keys", TEXT_X, KEYS_Y);

	    if(packet.items.containsKey(Types.Item.F_BOOT) && packet.items.get(Types.Item.F_BOOT))
            graphics.setColor(Color.red);
	    else
            graphics.setColor(Color.gray);
            graphics.fillRect(x_pos, y_boot, OFFSET, OFFSET);
	    if(packet.items.containsKey(Types.Item.R_KEY) && packet.items.get(Types.Item.R_KEY))
            graphics.setColor(Color.red);
        else
            graphics.setColor(Color.gray);
        graphics.fillRect(x_pos, y_key, OFFSET, OFFSET);

        x_pos += BOX_SPACE;

	    if(packet.items.containsKey(Types.Item.W_BOOT) && packet.items.get(Types.Item.W_BOOT))
            graphics.setColor(Color.blue);
	    else
            graphics.setColor(Color.gray);
        graphics.fillRect(x_pos, y_boot, OFFSET, OFFSET);
	    if(packet.items.containsKey(Types.Item.B_KEY) && packet.items.get(Types.Item.B_KEY))
            graphics.setColor(Color.blue);
	    else
		graphics.setColor(Color.gray);
	    graphics.fillRect(x_pos, y_key, OFFSET, OFFSET);

	    x_pos += BOX_SPACE;

	    if(packet.items.containsKey(Types.Item.I_BOOT) && packet.items.get(Types.Item.I_BOOT))
            graphics.setColor(Color.white);
        else
            graphics.setColor(Color.gray);
        graphics.fillRect(x_pos, y_boot, OFFSET, OFFSET);
	    if(packet.items.containsKey(Types.Item.Y_KEY) && packet.items.get(Types.Item.Y_KEY))
            graphics.setColor(Color.yellow);
        else
            graphics.setColor(Color.gray);
        graphics.fillRect(x_pos, y_key, OFFSET, OFFSET);

        x_pos += BOX_SPACE;

	    if(packet.items.containsKey(Types.Item.M_BOOT) && packet.items.get(Types.Item.M_BOOT))
            graphics.setColor(Color.green);
        else
            graphics.setColor(Color.gray);
        graphics.fillRect(x_pos, y_boot, OFFSET, OFFSET);
	    if(packet.items.containsKey(Types.Item.G_KEY) && packet.items.get(Types.Item.G_KEY))
            graphics.setColor(Color.green);
        else
            graphics.setColor(Color.gray);
        graphics.fillRect(x_pos, y_key, OFFSET, OFFSET);

        // Paint board
        int img_y = OFFSET;
                
        int playerX = player_pos[X][MY_INDEX];
        int playerY = player_pos[Y][MY_INDEX];
        
        send_packet(playerX - VIEW_LIMIT, playerY - VIEW_LIMIT, null, null, Types.ChangeSet.TILE);
        String[][] tiles = get_tiles();
        
        int xind = 0;
        for(int x = playerX - VIEW_LIMIT; x <= playerX + VIEW_LIMIT; x++, xind++){
            int img_x = OFFSET;
            int yind = 0;
            for(int y = playerY - VIEW_LIMIT; y <= playerY + VIEW_LIMIT; y++, yind++){
                // Send tile request packet with only x and y set
                graphics.drawImage(chips440.main.ResourceCache.getImage(tiles[xind][yind]), img_x, img_y, this);
                
                for(int player = player_pos[X].length - 1; player >= 0; player--){
                    if(x == player_pos[X][player] && y == player_pos[Y][player])
                        graphics.drawImage(chips440.main.ResourceCache.getImage(player), img_x, img_y, this);
                }
                
                img_x += IMAGE_SIZE;
            }
            img_y += IMAGE_SIZE;
        }
                
        strategy.show();
    }
    
    /**
     * Have you won? lost? other?
     */
    private void state_check(){
        // Has the game been won / lost?
        if(game_state == Types.EngineState.DEAD){
            playing = false;  // Stop playing!
            waiting = true;   // Start waiting!
            JOptionPane.showMessageDialog(null,
                    "You Lose!\nGood Day Sir!",
                    "LOSER!",
                    JOptionPane.PLAIN_MESSAGE);
        }
    }

    /**
     * Sends a packet with a particular request
     * to the server
     */
    private synchronized void send_packet(int tileX, int tileY, Types.Direct dir, Types.Item item, Types.ChangeSet changes){
        try{
            packet.tileX = tileX;
            packet.tileY = tileY;
            packet.direct = dir;
            packet.changes = changes;
            packet.offering = item;
            
            ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
            out.writeObject(packet);
            out.flush();
            //out.close();
        }catch(IOException io){
            playing = false;
            waiting = true;
            io.printStackTrace();
            failureMessage("Connection Lost: Could not send packet.");
        }
    }

    /**
     * Retrieves a response from the server
     */
    private synchronized void get_packet(){
        try{
            ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
            packet = (Packet)in.readObject();
            //in.close();
        }catch(Exception io){
            playing = false;
            waiting = true;
            failureMessage("Connection Lost: Could not receive packet.");
        }
    }

    private synchronized String[][] get_tiles(){
        get_packet();
        String[][] tiles = packet.tile;
        return tiles;
    }

    /**
     * Prints a fun error message if the server goes down
     */
    private void failureMessage(String msg){
        JOptionPane.showMessageDialog(null,
                    msg,
                    "FAILURE!",
                    JOptionPane.PLAIN_MESSAGE);
    }

    /*
     **********************
     * KeyListener Methods
     **********************
     */

    /**
     * a keyListener method; This method determines the
     * desired move by the player and sends a move request to
     * the server.  No response from the server is required.
     */
    public void keyPressed(KeyEvent event){
        int key = event.getKeyCode();
        Types.Direct dir = null;

        // Determine the direction to move in
        switch(key){
            case KeyEvent.VK_LEFT:
                dir = Types.Direct.LEFT;
                break;
            case KeyEvent.VK_RIGHT:
                dir = Types.Direct.RIGHT;
                break;
            case KeyEvent.VK_UP:
                dir = Types.Direct.UP;
                break;
            case KeyEvent.VK_DOWN:
                dir = Types.Direct.DOWN;
                break;
        }

        // Send the direction to move in
        if(dir != null){
            send_packet(0, 0, dir, null, Types.ChangeSet.MOVE);
            return;
        }
        
        Types.Item item = null;
        
        switch(key){
            case KeyEvent.VK_1:
                if(event.isShiftDown())
                    item = Types.Item.F_BOOT;
                else
                    item = Types.Item.R_KEY;
                break;
            case KeyEvent.VK_2:
                if(event.isShiftDown())
                    item = Types.Item.W_BOOT;
                else
                    item = Types.Item.B_KEY;
                break;
            case KeyEvent.VK_3:
                if(event.isShiftDown())
                    item = Types.Item.I_BOOT;
                else
                    item = Types.Item.Y_KEY;
                break;
            case KeyEvent.VK_4:
                if(event.isShiftDown())
                    item = Types.Item.M_BOOT;
                else
                    item = Types.Item.G_KEY;
                break;
        }
        
        if(item != null){
            send_packet(0, 0, null, item, Types.ChangeSet.TRADE);
        }
    }

    /**
     * a keyListener method; this method is not implemented.
     */
    public void keyReleased(KeyEvent event){}

    /**
     * a keyListener method; this method is not implemented.
     */
    public void keyTyped(KeyEvent event){}
    
    

    /*
     **********************************
     * Internal ActionListener Classes
     **********************************
     */
    
    /**
     * the ActionListener for the Quit menu button Quits the game.
     */
    private class Quit implements ActionListener{
        public void actionPerformed(ActionEvent event){
            theme.stop();
            if(socket != null){
                try{
                    send_packet(0, 0, null, null, Types.ChangeSet.QUIT);
                    socket.close();
                }catch(IOException i){}
            }
            System.exit(0);
        }
    }

    /**
     * The ActionListener for the Rules menu button - displays the game rules
     */
    private class Rules implements ActionListener{
        public void actionPerformed(ActionEvent event){
            JOptionPane.showMessageDialog(null,
                    "Collect all the chips and get to the exit before time"+
                    "\nruns out. But be careful: some tiles are deadly!"+
                    "\nPick up keys to open doors and boots to walk safely"+
                    "\non dangerous surfaces!"+
                    "\nAlso remember that keys can only be used ONCE!"+
                    "\n\nMovement:"+
                    "\nUse the arrow keys to move."+
                    "\n\nGiving Items:"+
                    "\nKeys: 1,2,3,4, Boots: shift+1,2,3,4",
                    "Chips Challenge 440 Rules",
					  JOptionPane.PLAIN_MESSAGE);
        }
    }

    /**
     * The ActionListener for the About menu button - displays the info about the game.
     */
    private class About implements ActionListener{
        public void actionPerformed(ActionEvent event){
            JOptionPane.showMessageDialog(null,
                    "Chips Challenge 440:"+
                    "\nWritten by Ryan Hofler and Ian Voysey"+
                    "\nfor 15-440 Fall 2008.",
                    "About Chips Challenge 440",
                    JOptionPane.PLAIN_MESSAGE);
        }
    }

    /**
     * the ActionListener for the New Game menu button Restarts the game.
     *
     * @author Ryan Hofler
     * @version 0.1
     */
    private class NewGame implements ActionListener{
     
        public void actionPerformed(ActionEvent event){
            requestFocus();
            waiting = false;  // Stop waiting for a new game
            playing = false;   // Stop playing prev game
        }
    }
}